 /*******************************************************************************
  * Copyright (c) 2005, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.baseadaptor.bundlefile;

 import java.io.*;
 import java.util.*;
 import java.util.zip.ZipEntry ;
 import java.util.zip.ZipFile ;
 import org.eclipse.osgi.baseadaptor.BaseData;
 import org.eclipse.osgi.framework.debug.Debug;
 import org.eclipse.osgi.internal.baseadaptor.AdaptorMsg;
 import org.eclipse.osgi.internal.baseadaptor.AdaptorUtil;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.FrameworkEvent;

 /**
  * A BundleFile that uses a ZipFile as it base file.
  * @since 3.2
  */
 public class ZipBundleFile extends BundleFile {
     protected static MRUBundleFileList mruList = new MRUBundleFileList();

     /**
      * The bundle data
      */
     protected BaseData bundledata;
     /**
      * The zip file
      */
     protected ZipFile zipFile;
     /**
      * The closed flag
      */
     protected boolean closed = true;

     /**
      * Constructs a ZipBundle File
      * @param basefile the base file
      * @param bundledata the bundle data
      * @throws IOException
      */
     public ZipBundleFile(File basefile, BaseData bundledata) throws IOException {
         super(basefile);
         if (!BundleFile.secureAction.exists(basefile))
             throw new IOException(NLS.bind(AdaptorMsg.ADAPTER_FILEEXIST_EXCEPTION, basefile));
         this.bundledata = bundledata;
         this.closed = true;
     }

     /**
      * Checks if the zip file is open
      * @return true if the zip file is open
      */
     protected boolean checkedOpen() {
         try {
             return getZipFile() != null;
         } catch (IOException e) {
             if (bundledata != null)
                 bundledata.getAdaptor().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, bundledata.getBundle(), e);
             return false;
         }
     }

     /**
      * Opens the ZipFile for this bundle file
      * @return an open ZipFile for this bundle file
      * @throws IOException
      */
     protected ZipFile basicOpen() throws IOException {
         return BundleFile.secureAction.getZipFile(this.basefile);
     }

     /**
      * Returns an open ZipFile for this bundle file. If an open
      * ZipFile does not exist then a new one is created and
      * returned.
      * @return an open ZipFile for this bundle
      * @throws IOException
      */
     protected synchronized ZipFile getZipFile() throws IOException {
         if (closed) {
             mruList.add(this);
             zipFile = basicOpen();
             closed = false;
         } else
             mruList.use(this);
         return zipFile;
     }

     /**
     * Returns a ZipEntry for the bundle file. Must be called while synchronizing on this object.
     * This method does not ensure that the ZipFile is opened. Callers may need to call getZipfile() prior to calling this
     * method.
     * @param path the path to an entry
     * @return a ZipEntry or null if the entry does not exist
     */
     protected ZipEntry getZipEntry(String path) {
         if (path.length() > 0 && path.charAt(0) == '/')
             path = path.substring(1);
         ZipEntry entry = zipFile.getEntry(path);
         if (entry != null && entry.getSize() == 0 && !entry.isDirectory()) {
             // work around the directory bug see bug 83542
 ZipEntry dirEntry = zipFile.getEntry(path + '/');
             if (dirEntry != null)
                 entry = dirEntry;
         }
         return entry;
     }

     /**
      * Extracts a directory and all sub content to disk
      * @param dirName the directory name to extract
      * @return the File used to extract the content to. A value
      * of <code>null</code> is returned if the directory to extract does
      * not exist or if content extraction is not supported.
      */
     protected synchronized File extractDirectory(String dirName) {
         if (!checkedOpen())
             return null;
         Enumeration entries = zipFile.entries();
         while (entries.hasMoreElements()) {
             String entryPath = ((ZipEntry ) entries.nextElement()).getName();
             if (entryPath.startsWith(dirName) && !entryPath.endsWith("/")) //$NON-NLS-1$
 getFile(entryPath, false);
         }
         return getExtractFile(dirName);
     }

     protected File getExtractFile(String entryName) {
         if (bundledata == null)
             return null;
         String path = ".cp"; /* put all these entries in this subdir *///$NON-NLS-1$
 String name = entryName.replace('/', File.separatorChar);
         if ((name.length() > 1) && (name.charAt(0) == File.separatorChar)) /* if name has a leading slash */
             path = path.concat(name);
         else
             path = path + File.separator + name;
         return bundledata.getExtractFile(path);
     }

     public synchronized File getFile(String entry, boolean nativeCode) {
         if (!checkedOpen())
             return null;
         ZipEntry zipEntry = getZipEntry(entry);
         if (zipEntry == null)
             return null;

         try {
             File nested = getExtractFile(zipEntry.getName());
             if (nested != null) {
                 if (nested.exists()) {
                     /* the entry is already cached */
                     if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                         Debug.println("File already present: " + nested.getPath()); //$NON-NLS-1$
 if (nested.isDirectory())
                         // must ensure the complete directory is extracted (bug 182585)
 extractDirectory(zipEntry.getName());
                 } else {
                     if (zipEntry.getName().endsWith("/")) { //$NON-NLS-1$
 if (!nested.mkdirs()) {
                             if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                                 Debug.println("Unable to create directory: " + nested.getPath()); //$NON-NLS-1$
 throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, nested.getAbsolutePath()));
                         }
                         extractDirectory(zipEntry.getName());
                     } else {
                         InputStream in = zipFile.getInputStream(zipEntry);
                         if (in == null)
                             return null;
                         /* the entry has not been cached */
                         if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                             Debug.println("Creating file: " + nested.getPath()); //$NON-NLS-1$
 /* create the necessary directories */
                         File dir = new File (nested.getParent());
                         if (!dir.exists() && !dir.mkdirs()) {
                             if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                                 Debug.println("Unable to create directory: " + dir.getPath()); //$NON-NLS-1$
 throw new IOException(NLS.bind(AdaptorMsg.ADAPTOR_DIRECTORY_CREATE_EXCEPTION, dir.getAbsolutePath()));
                         }
                         /* copy the entry to the cache */
                         AdaptorUtil.readFile(in, nested);
                         if (nativeCode)
                             setPermissions(nested);
                     }
                 }

                 return nested;
             }
         } catch (IOException e) {
             if (Debug.DEBUG && Debug.DEBUG_GENERAL)
                 Debug.printStackTrace(e);
         }
         return null;
     }

     public synchronized boolean containsDir(String dir) {
         if (!checkedOpen())
             return false;
         if (dir == null)
             return false;

         if (dir.length() == 0)
             return true;

         if (dir.charAt(0) == '/') {
             if (dir.length() == 1)
                 return true;
             dir = dir.substring(1);
         }

         if (dir.length() > 0 && dir.charAt(dir.length() - 1) != '/')
             dir = dir + '/';

         Enumeration entries = zipFile.entries();
         ZipEntry zipEntry;
         String entryPath;
         while (entries.hasMoreElements()) {
             zipEntry = (ZipEntry ) entries.nextElement();
             entryPath = zipEntry.getName();
             if (entryPath.startsWith(dir)) {
                 return true;
             }
         }
         return false;
     }

     public synchronized BundleEntry getEntry(String path) {
         if (!checkedOpen())
             return null;
         ZipEntry zipEntry = getZipEntry(path);
         if (zipEntry == null) {
             if (path.length() == 0 || path.charAt(path.length() - 1) == '/') {
                 // this is a directory request lets see if any entries exist in this directory
 if (containsDir(path))
                     return new DirZipBundleEntry(this, path);
             }
             return null;
         }

         return new ZipBundleEntry(zipEntry, this);

     }

     public synchronized Enumeration getEntryPaths(String path) {
         if (!checkedOpen())
             return null;
         if (path == null)
             throw new NullPointerException ();

         if (path.length() > 0 && path.charAt(0) == '/')
             path = path.substring(1);
         if (path.length() > 0 && path.charAt(path.length() - 1) != '/')
             path = new StringBuffer (path).append("/").toString(); //$NON-NLS-1$

         Vector vEntries = new Vector();
         Enumeration entries = zipFile.entries();
         while (entries.hasMoreElements()) {
             ZipEntry zipEntry = (ZipEntry ) entries.nextElement();
             String entryPath = zipEntry.getName();
             if (entryPath.startsWith(path)) {
                 if (path.length() < entryPath.length()) {
                     if (entryPath.lastIndexOf('/') < path.length()) {
                         vEntries.add(entryPath);
                     } else {
                         entryPath = entryPath.substring(path.length());
                         int slash = entryPath.indexOf('/');
                         entryPath = path + entryPath.substring(0, slash + 1);
                         if (!vEntries.contains(entryPath))
                             vEntries.add(entryPath);
                     }
                 }
             }
         }
         return vEntries.size() == 0 ? null : vEntries.elements();
     }

     public synchronized void close() throws IOException {
         if (!closed) {
             closed = true;
             zipFile.close();
             mruList.remove(this);
         }
     }

     public void open() throws IOException {
         //do nothing
 }

     /**
      * Shutsdown the bundle file closer thread for zip bundle files
      */
     public static void shutdown() {
         mruList.shutdown();
     }
 }

